/*
 *  Level.m
 *  pogostick
 *
 *  Created by chuck@kollar.com on 10/11/09.
 *  Copyright 2009 Charles P. Kollar All rights reserved.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#import "Level.h"

/*
 This is the code that builds the various levels. We have seen a number
 of ways that this is done in the cocos2d-iphone examples. Some are created
 in the code, others are done through xml files. We wanted to be able to
 visualize the levels and also to have them generated automatically. The method
 chosen was to use the output from the open source drawing package Inkscape.
 http://www.inkscape.org
 Inkscape produces SVG (Scalable Vector Graphics) files to represent the
 drawings, and even allows the user to exit the XML (SVG).
 
 This program uses the id property of the SVG item to determine what sort of
 game item is being represented. Items who's id begins with "floor" are taken
 to be a description of the floor. A table showing the id tags and their
 game elements is as follows:
 floor*          a path representing a floor with pills suspended over it.
 vplatform       a rectangle representing a platform that moves vertically
 hplatorm        a rectangle representing a platform that moves horizontally
 pillcage        a rectangle representing a pill cage
 personfaceleft  a rectangle representing a person facing left
 personfaceright a rectangle representing a person facing right
 lem             a rectangle representing a lunar explorer module
 exit            a rectangle representing an exit to the next to another level.
 
 A level is "associated with a "GameLayer". The gamelayer holds the interactive
 part of the game where the "Level" holds the instances of the stuff in it and
 generally the actions on them. The gamelayer generally holds the touch and timing
 stuff, changes to the things are done through the level.
 
 There is a level that overlays the GameLayer (found in the same file) which
 is called the "OverlayLayer". This is the place where the sliders are placed
 and feedbacks to the user generally happen.
 
 Some of the interesting problems here are trying to integrate an object oriented
 system with a procedural one (Chipmunk Physics). I have tried to keep the use
 of global variables to a minimum, erroring on the side of simplicity.
*/

@implementation Level

@synthesize path;
@synthesize space;
@synthesize staticBody;
@synthesize stick;
@synthesize initialStickPosition;
@synthesize floors;
@synthesize persons;
@synthesize bugs;
@synthesize lems;
@synthesize pillCages;
@synthesize platforms;
@synthesize doors;
@synthesize drawingWidth;
@synthesize drawingHeight;
@synthesize emitter;
@synthesize thrustEmitter;



// These are defined in GameLayer.m. They need to be attached to a 'Layer'.
// This object isn't a layer, it's just a place to hold objects that exist
// in a level.
extern ParticleSystem *emitter;
extern ParticleSystem *thrustEmitter;

// set in menu.m by the user from yes/no buttons...
extern int soundp, musicp;


// The Class 'next' method is a factory method that returns a newly created level.
// So this init method is private, called only by the next class method.
-(id)initInSpace:(cpSpace *)sp fileName:(NSString *)fn ParseError:(NSError **)error
{
    if (self = [super init]) {
        space = sp;
        
        /* This is the rigid body that we will be attaching our ground line
         segments to. We don't want it to move, so we give it an infinite
         mass and moment of inertia. We also aren't going to add it to our
         space. If we did, it would fall under the influence of gravity,
         and the ground would fall with it. */
        staticBody = cpBodyNew(INFINITY, INFINITY);
        
        // Here we keep all of the highlevel objects that the level contains.
        // The objects are created by parsing the SVG file that is createed
        // with a line drawing package like 'Inkscape'.
        floors = [[NSMutableArray alloc] initWithCapacity: 3];
        persons = [[NSMutableArray alloc] initWithCapacity: 3];
        bugs = [[NSMutableArray alloc] initWithCapacity: 2];
        lems = [[NSMutableArray alloc] initWithCapacity: 2];
        pillCages = [[NSMutableArray alloc] initWithCapacity: 3];
        platforms = [[NSMutableArray alloc] initWithCapacity: 3];
        doors = [[NSMutableArray alloc] initWithCapacity: 2];
        
        // Only one copy of this is maintained for all instances...
        if (bulletsToDelete == nil) bulletsToDelete = [[NSMutableArray alloc] initWithCapacity: 10];
        
        // Here we parse the SVG file that contains the information that is needed
        // to create the objects in this level.
        NSBundle *bundle = [NSBundle mainBundle];
        path = [[bundle pathForResource:fn ofType:@"svg"] retain];
        NSURL *url = [NSURL fileURLWithPath: path isDirectory: NO];
        NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
        
        // Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
        [parser setDelegate:self];
        
        [parser setShouldProcessNamespaces:NO];
        [parser setShouldReportNamespacePrefixes:NO];
        [parser setShouldResolveExternalEntities:NO];
        
        [parser parse];
        
        //IS THERE A MEMORY LEAK HERE???
        NSError *parseError = [parser parserError];
        if (parseError && error) {
            *error = parseError;
        }
        
        [parser release];
        
        ///
        /// NOTE: Most of the call backs are defined here or in the parser method below
        /// if the callback is specific to the object created.
        
        // The instance of this level is saved by the cp callback.
        // If there is ever more than one level instance at a time, this will need
        // to be rethought. But for now it works...
        cpSpaceAddCollisionHandler(space, kColl_Pill2, kColl_Stick, &pill2HitStick, NULL, NULL, NULL, self);
        cpSpaceAddCollisionHandler(space, kColl_Pill2, kColl_Foot, &pill2HitStick, NULL, NULL, NULL, self);
        // The pogostick kills the shooter...
        cpSpaceAddCollisionHandler(space, kColl_Foot, kColl_Head, &footHitHeadEvent, NULL, NULL, NULL, self);
        // The shooter hits the pogostick...
        cpSpaceAddCollisionHandler(space, kColl_Bullet, kColl_Stick, &bulletHitStickEvent, NULL, NULL, NULL, self);
        cpSpaceAddCollisionHandler(space, kColl_Bullet, kColl_Foot, &bulletHitStickEvent, NULL, NULL, NULL, self);
    }
    
    return self;
}

// For floors we start the colission type off at 100, it grows up from there.
// better yet would be a class that hands new tickets out and keeps track of what got them...
cpCollisionType nextFloorColissionType = FLOOR_STARTING_COLISSION_TYPE_VALUE;
cpCollisionType nextPillCageColissionType = PILLCAGE_STARTING_COLISSION_TYPE_VALUE;

- (void)parser:(NSXMLParser *) __attribute__ ((unused)) parser didStartElement:(NSString *)elementName namespaceURI:(NSString *) __attribute__ ((unused)) namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
    // Since so much is created here we make a pool for this method...
    NSAutoreleasePool *parserPool = [[NSAutoreleasePool alloc] init];
	
    if (qName) {
		elementName = qName;
	}
	
    // Here we find out how big the drawing is. To change this to the right size in Inkscape
    // do 'File > Document Properties', then in the section labeled 'Custom size' chage the Width and
    // Height to something that makes more sense given what you have put in the layer.
    //
    // HUGE NOTE: Is is necessary that you change the size of the drawing AAANNNDDD save the
    // drawing BEFORE you add any objects to the svg drawing as Inkscape makes all data relative
    // to what ever the current page size and it doesn't recognize a change in the page size
    // till the document is changed!!!!!
    //
    // The other problem needs to be handled by this code. That is the drawing (svg file) has
    // it's <0,0> in the top right hand corner and it increases going down and to the right.
    // However, the <0,0> of the layer is in the bottom right hand corner increasing up and to
    // the right. So, the first dimension is reversed in the drawing from that of the layer.
    // To fix this we need to map points against the 'Height' so that they are reversed.
	if ([elementName isEqualToString:@"svg"]) {
        NSString *dw= [attributeDict valueForKey:@"width"];
        NSString *dh = [attributeDict valueForKey:@"height"];
        drawingWidth = [dw floatValue];
        drawingHeight = [dh floatValue];
        setLowestFloorY(drawingHeight);
        
	} else if ([elementName isEqualToString:@"path"]) {
        NSString *ident = [attributeDict valueForKey:@"id"];
        NSString *dat = [attributeDict valueForKey:@"d"];
        NSString *transform = [attributeDict valueForKey:@"transform"];
        if ([ident length] > 5 && [[ident substringToIndex: 5] isEqualToString:@"floor"]) {
            // id must be of the form "floorNNNN"
            // The 'Floor' object uses the 'PathParser' to parse the SVG path information.
            // Each floor get's a different cpCollisionType so that we cab identify which floor
            // the stick hit from the callback...
            //NSLog(@"PARSER... id '%s' d '%s' transform '%s'\n", [ident UTF8String], [dat UTF8String], [transform UTF8String]);
            Floor *f = 
                [[Floor alloc] 
                 initInSpace:space toStaticBody:staticBody identifier:ident collisionType:++nextFloorColissionType group:1 spaceHeight:drawingHeight data:dat];
            if (f != nil) {
                [floors addObject: f];
                // create a callback for for the floor for the colission type above.
                // The one call back will handle all pogostick colissions with all floors on this level
                cpSpaceAddCollisionHandler(space, kColl_Foot, nextFloorColissionType, &footHitFloorEvent, NULL, NULL, NULL, self);
                // to keep the bullets from littering the ground...
                cpSpaceAddCollisionHandler(space, kColl_Bullet, nextFloorColissionType, &bulletHitGroundEvent, NULL, NULL, NULL, self);
                // The pills that area attached to the space are associated with a given floor
                cpSpaceAddCollisionHandler(space, kColl_Pill, kColl_Stick, &stickHitPillEvent, NULL, NULL, NULL, self);
                cpSpaceAddCollisionHandler(space, kColl_Pill, kColl_Foot, &stickHitPillEvent, NULL, NULL, NULL, self);
                // pogostick collides with the bug...
                cpSpaceAddCollisionHandler(space, kColl_Bug, kColl_Foot, &footHitBugEvent, NULL, NULL, NULL, self);
                [f release];
            }
        } else if ([ident length] > 4 && [[ident substringToIndex:4] isEqualToString:@"exit"]) {
            // id must be of the form "exitNNNN"
            
        }
        
    } else if ([elementName isEqualToString:@"rect"]) {
        //
        // BIG NOTE:
        // We need to be able to handle the 'transform' attribute with a 'translate(x,y)' value.
        // This is generally added by Inkscape when the size of the drawing has been changed.
        //
        NSString *transform = [attributeDict valueForKey:@"transform"];
        NSString *ident = [attributeDict valueForKey:@"id"];
        NSString *xStr = [attributeDict valueForKey:@"x"];
        NSString *yStr = [attributeDict valueForKey:@"y"];
        NSString *widthStr = [attributeDict valueForKey:@"width"];
        NSString *heightStr = [attributeDict valueForKey:@"height"];
        cpFloat x = [xStr floatValue];
        cpFloat y = [yStr floatValue];
        cpFloat width = [widthStr floatValue];
        cpFloat height = [heightStr floatValue];
        // The <x,y> from Inkscape is always the top right hand corner of the item.
        // But the sense is different on the 'y' axis. The 'p' should be the center
        // of the item in our world.
        cpVect p = cpv(x+(width/2),drawingHeight-y-(height/2));
        cpVect size = cpv(width, height);
        //NSLog(@"PARSER... id '%s' rect p <%f,%f> size <%f,%f>\n", [ident UTF8String], p.x, p.y, size.x, size.y);
        
        if ([ident length] > 4 && [[ident substringToIndex:4] isEqualToString:@"door"]) {
            Door *d = [[Door alloc] initInSpace:space toStaticBody:staticBody position:p size:size];
            if (d != nil) {
              [doors addObject: d];
              [d release];
            }
        } else if ([ident length] > 18 && [[ident substringToIndex:18] isEqualToString:@"pillcagefacingleft"]) {
            PillCage *pc = 
                [[PillCage alloc]
                    initInSpace:space toStaticBody:staticBody identifier:ident collisionType:++nextPillCageColissionType facingLeft:YES atPosition:p size:size];
            if (pc != nil) {
                [pillCages addObject: pc];
                [pc release];
                // the collision that opens the PillCage and notes the last touch down point (sort of a floor) of the stick...
                cpSpaceAddCollisionHandler(space, kColl_Foot, nextPillCageColissionType, &footHitReleasePillEvent, NULL, NULL, NULL, self);
            }
        } else if ([ident length] > 19 && [[ident substringToIndex:19] isEqualToString:@"pillcagefacingright"]) {
            PillCage *pc = 
                [[PillCage alloc]
                    initInSpace:space toStaticBody:staticBody identifier:ident collisionType:++nextPillCageColissionType facingLeft:NO atPosition:p size:size];
            if (pc != nil) {
                [pillCages addObject: pc];
                [pc release];
                // the collision that opens the PillCage and notes the last touch down point (sort of a floor) of the stick...
                cpSpaceAddCollisionHandler(space, kColl_Foot, nextPillCageColissionType, &footHitReleasePillEvent, NULL, NULL, NULL, self);
            }
        } else if ([ident length] > 17 && [[ident substringToIndex:17] isEqualToString:@"personfacingright"]) {
            Person *pers =
                [[Person alloc]
                    initInSpace:space toStaticBody:staticBody identifier:ident
                        gravity:SPACE_GRAVITY group:4 position:p size:size];
            // various joint rotations to make the person look like they are standing and shooting...
            [pers faceRight];
            [persons addObject: pers];
            [pers release];
        } else if ([ident length] > 16 && [[ident substringToIndex:16] isEqualToString:@"personfacingleft"]) {
            Person *pers = 
                [[Person alloc]
                    initInSpace:space toStaticBody:staticBody identifier:ident
                        gravity:SPACE_GRAVITY group:4 position:p size:size];
            // various joint rotations to make the person look like they are standing and shooting...
            [pers faceLeft];
            [persons addObject: pers];
            [pers release];
        } else if ([ident length] > 9 && [[ident substringToIndex:9] isEqualToString:@"vplatform"]) {
            VerticalPlatform *vp =
                [[VerticalPlatform alloc]
                    initInSpace:space staticBody:staticBody identifier:ident atPosition:p size:size];
            [platforms addObject: vp];
            [vp release];
        } else if ([ident length] > 11 && [[ident substringToIndex:11] isEqualToString:@"arcplatform"]) {
            ArcPlatform *ap =
                [[ArcPlatform alloc]
                 initInSpace:space staticBody:staticBody identifier:ident atPosition:p size:size];
            [platforms addObject: ap];
            [ap release];
        } else if ([ident length] > 3 && [[ident substringToIndex:3] isEqualToString:@"lem"]) {
            LEM *lem = [[LEM alloc] initInSpace:space toStaticBody:staticBody group:3
                                       position:p size:size flightMax:cpv(drawingHeight,drawingWidth)];
            [lems addObject: lem];
            [lem release];
        } else if ([ident length] > 3 && [[ident substringToIndex:3] isEqualToString:@"bug"]) {
            // if you hit the bug... you go bonkers....
            Bug *bug = [[Bug alloc] initInSpace: space group:5 position: p size: size];
            [bugs addObject: bug];
            [bug release];
        } else if ([ident length] >= 9 && [[ident substringToIndex:9] isEqualToString:@"pogostick"]) {
            // we should check to make sure that there is only one of these...
            // Here 'position' is the center of the pogostick.
            initialStickPosition = p;
            stick = [[PogoStick alloc]
                        initInSpace:space toStaticBody:staticBody
                        gravity:SPACE_GRAVITY group:2 position:p size:size];	
        } else {
            NSLog(@"Error while parsing level file '%s'. Unknown item id '%s'?\n", [path UTF8String], [ident UTF8String]);
        }
    }
    
    //NSLog(@"Lowest Y point: %f", getLowestFloorY());
    [parserPool drain];
}

- (void)parser:(NSXMLParser *) __attribute__ ((unused)) parser parseErrorOccurred:(NSError *)parseError{
	NSLog(@"Error on XML Parse: %@", [parseError localizedDescription] );
}

#define DELETED_SHAPES_SIZE         20
cpShape *deletedShapes[DELETED_SHAPES_SIZE];
int deletedShapesNext = 0;

// remove the bullet from the screen after the colission callback has finished...
-(void) processQueuedBulletHitGroundEvents {
	NSInteger cnt = [bulletsToDelete count];
	if (cnt == 0) return;
    //NSLog(@"processQueuedBulletHitGroundEvents cnt = %d", cnt);
    // remove the pill...
	for (int i = cnt-1; i >= 0; --i) {
		ShapeWrapper *item = [[bulletsToDelete objectAtIndex: (unsigned)i] retain];
        [bulletsToDelete removeObjectIdenticalTo:item];
        [bulletsToDelete removeObject:item];
        [item cpFree];
        [item release]; // this does a free but not instantly
	}
}

-(BOOL) stickInDoor {
    // Did the pogostick go through a next level door?
    NSEnumerator *e = [doors objectEnumerator];
    id iv;
    while (iv = [e nextObject]) {
        Door *d = (Door *)iv;
        cpBody *stickBody = [[self stick] stickBody];
        cpFloat stickPx = stickBody->p.x;
        cpFloat stickPy = stickBody->p.y;
        cpFloat doorPx = [d body]->p.x;
        cpFloat doorPy = [d body]->p.y;
        cpFloat deltaX = (doorPx > stickPx) ? doorPx - stickPx : stickPx - doorPx;
        cpFloat deltaY = (doorPy > stickPy) ? doorPy - stickPy : stickPy - doorPy;
        if (deltaX <= [d width]/2 && deltaY <= [d height]/2) {
            NSLog(@"Though door to next level!");
            return YES;
        } 
    }
    return NO;
}

// Here we do all of the action method for ALL those things that need it
// that are kept in the level right here...
//
// Returns YES if there is life left, and NO if there is not...
-(BOOL) performActions {
    NSEnumerator *e;
    id iv;
    
    e = [persons objectEnumerator];
    while (iv = [e nextObject]) [iv performActions]; //all Person instances in this layer
    
    e = [pillCages objectEnumerator];
    while (iv = [e nextObject]) [iv performActions]; //all PillCages instances in this layer
    
    // ParticleSystem *emitter = (ParticleSystem *) [self getChildByTag:kTagLabelEmitter];
    e = [floors objectEnumerator];
    while (iv = [e nextObject])
        [iv processQueuedStickHitPillEventsWithEmitter:emitter]; //all Floor instances in this layer

    e = [platforms objectEnumerator];
    while (iv = [e nextObject]) [iv performActions]; //all Platform instances in this layer
    
    e = [lems objectEnumerator];
    while (iv = [e nextObject]) [iv performActions];
    
    e = [bugs objectEnumerator];
    while (iv = [e nextObject]) [iv performActions];
    
    [self processQueuedBulletHitGroundEvents];
    
    // This is defined in GameLayer.m...
    extern int currentScore;
    // Did the pogostick fall to it's doom?
    cpBody *sb = [[self stick] stickBody];
    if (sb->p.y < getLowestFloorY()) {
        // The center point of the pogostick is below the lowest floor Y point...
        // So it can't bounce on anything any more; from this point on the
        // game is going to be quite boring. So we just say that the pogostick died.
        // Sniff...
        NSLog(@"Ooops... Mr. Pogostick fell off of the world!");
        
        // put the stick back on the playing field at it's starting point...
        [[self stick] moveToPosition: initialStickPosition];
        
        if (soundp != 0) {
            [[SimpleAudioEngine sharedEngine] playEffect:STICK_DIES_WAV];
        }
        
        // remove a life icon...
        currentScore = ((currentScore / POINTS_PER_LIFE) * POINTS_PER_LIFE) - 1;
    }
    if (currentScore < 0) return NO;
    return YES;
}

-(void) dealloc {
    NSLog(@"Level dealloc"); 
    // Frees all referenced entities. (bodies, shapes and constraints)
    cpSpaceFreeChildren(space);
    cpSpaceFree(space);
    
    [path release];
    [stick release];
   
    [floors release];
    [persons release];
    [bugs release];
    [lems release];
    [pillCages release];
    [platforms release];
    [doors release];

	[super dealloc];
}

@end
